package serv

import (
	"encoding/json"
	"fmt"
	"github.com/go-redis/redis"
	"math/rand"
	"net/rpc"
	"reflect"
	"time"
)

/*
serv包实现了本地服务和远程服务的注册、连接和切换
对于本地的服务，服务端和客户端实际上都是被调用的包里实现的那个实例
	被调用的模块初始化时，注册本地服务，如果要调用服务的应用也在本地，就能直接连接
对于远程的服务，客户端指的是contract包中实现的用来调用rpc的实例
	被调用的模块创建远程服务时，将服务名称、地址、网络协议等信息注册，要调用服务的应用通过rpc就能调用
 */
var Redis *redis.Client
var serverMap = make(map[string]interface{}) // 本地服务
var clientListMap = make(map[string][]interface{}) // 需要连接本地服务的客户端

// 注册一个本地的服务
func Local(name string, s interface{})  {
	serverMap[name] = s
	for i, _ := range clientListMap[name] {
		reflect.ValueOf(clientListMap[name][i]).Elem().Set(reflect.ValueOf(s))
	}
}

// 连接一个本地的服务
func Direct(name string, c interface{})  {
	if s, ok := serverMap[name]; ok {
		reflect.ValueOf(c).Elem().Set(reflect.ValueOf(s))
	}
	clientListMap[name] = append(clientListMap[name], c)
}



type ServiceNode struct {
	Name string `json:"name"`
	Network string `json:"network"`
	Address string `json:"address"`
	Time int64 `json:"time"`
}

// 注册在一个地址上的多个远程服务
func Register(network, address string, services ...string)  {
	now := time.Now().Unix()
	for _, name := range services {
		key := "Service."+name
		field := network+"://"+address
		value, _ := json.Marshal(ServiceNode{
			Name:    name,
			Network: network,
			Address: address,
			Time:now,
		})
		go func(key, field, value string) {
			for {
				_, err := Redis.HSet(key, field, value).Result()
				if err != nil {
					fmt.Println("注册失败", err)
					return
				}
				time.Sleep(time.Second*10)
			}
		}(key, field, string(value))
	}
}

// 返回一个支持服务的服务器的客户端
func Select(name string) *rpc.Client {
	now := time.Now().Unix()
	nm := Redis.HGetAll("Service." + name).Val()
	ns := make([]ServiceNode, 0)
	for _, value := range nm {
		node := ServiceNode{}
		if err := json.Unmarshal([]byte(value), &node); err != nil {
			continue
		}
		if node.Time + 30 > now {
			ns = append(ns, node)
		}
	}
	if len(ns) > 0 {
		rand.Seed(now)
		node := ns[rand.Intn(len(ns))]
		client, err := rpc.DialHTTP(node.Network, node.Address)
		if err != nil {
			panic(err)
		}
		return client
	}
	return nil
}